
if(!window.Berta) window.Berta = {};

Berta.Server = new Class(function() {
	
	return {
		
		statics: {
			_instance: null, // Singleton instance
			instance: function(options) {
				if(!Berta.Server._instance) {
					Berta.Server._instance = new Berta.Server(options);
				}
				
				return Berta.Server._instance;
			}
		},
		
		options: {
			urlRESTBase: '', 	// base for REST URLS (without trailing slash)
			urlBase: ''			// base for all oher urls (without trailing slash)
		},
		
		offline: true,
		
		// file uploader
		uploaders: [],
		curUploaderIndex: 0,
		
		// xhrs
		xhrs: [],
		xhrCount: 0,
		
		// getter: returns event namespace for the instance
		namespace: function() { return '.Berta.Server'; },
		
		constructor: function(options) {
			$.extend(true, this.options, options);
			//this.uploader = Berta.Server.Upload;
		},
		
		
		
		//   .______    __    __  .______    __       __    ______    ////////////////////////////////////////////////////////////
		//   |   _  \  |  |  |  | |   _  \  |  |     |  |  /      |   ////////////////////////////////////////////////////////////
		//   |  |_)  | |  |  |  | |  |_)  | |  |     |  | |  ,----'   ////////////////////////////////////////////////////////////
		//   |   ___/  |  |  |  | |   _  <  |  |     |  | |  |        ////////////////////////////////////////////////////////////
		//   |  |      |  `--'  | |  |_)  | |  `----.|  | |  `----.   ////////////////////////////////////////////////////////////
		//   | _|       \______/  |______/  |_______||__|  \______|   ////////////////////////////////////////////////////////////
		
		
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//  REST API   /////////////////////////////////////////////////////////////////////////////////////////////////////////
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////	
		
		/**
		 * Loads data (GET)
		 * @param {String} command					REST command + variable name, e.g., /entries/1:name
		 * @param {Object} data						Data to send (null for nothing)
		 * @param {Object} params					Params to send (null for nothing; WITHOUT container variable)
		 * @param {Function} callbackComplete		Function to execute on complete
		 * @param {Function} callbackError			Function to execute on error
		 * @return {Number}	Action Id
		 */
		get: function(route, callbackComplete, callbackError) {
			return this._(ajax)('get', route, callbackComplete, callbackError, true);
		},
		
		/**
		 * Saves data (PUT)
		 * @param {String} command					REST command
		 * @param {Object} data						Data to send (null for nothing)
		 * @param {Function} callbackComplete		Function to execute on complete
		 * @param {Function} callbackError			Function to execute on error
		 * @return {Number}	Action Id
		 */
		put: function(route, callbackComplete, callbackError) {
			//$.log('Server:PUT' , command, data);
			return this._(ajax)('put', route, callbackComplete, callbackError, true);
		},
		
		/**
		 * Creates new stuff (POST)
		 * @param {String} command					REST command
		 * @param {Object} data						Data to send (null for nothing)
		 * @param {Function} callbackComplete		Function to execute on complete
		 * @param {Function} callbackError			Function to execute on error
		 * @return {Number}	Action Id
		 */
		post: function(route, callbackComplete, callbackError) {
			return this._(ajax)('post', route, callbackComplete, callbackError, true);
		},
		
		/**
		 * Deletes something (DELETE)
		 * @param {String} command					REST command
		 * @param {Object} data						Data to send (null for nothing)
		 * @param {Function} callbackComplete		Function to execute on complete
		 * @param {Function} callbackError			Function to execute on error
		 * @return {Number}	Action Id
		 */
		del: function(route, callbackComplete, callbackError) {
			return this._(ajax)('delete', route, callbackComplete, callbackError, true);
		},
		
		/**
		 * Cancels action with the specified ID
		 * @param {Number} id		Action id, that was returned from get/put/post/del method
		 */
		cancel: function(id) {
			if(this.xhrs[id]) {
				this.xhrs[id].abort();
				this.xhrs[id] = null;
			}
		},
		
		
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//  FILE LOAD   ////////////////////////////////////////////////////////////////////////////////////////////////////////
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		
		load: function(url, callbackComplete, callbackError) {
			return this._(ajax)('get', url, callbackComplete, callbackError, false);
		},
		
		
		
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//  FILE UPLOAD   //////////////////////////////////////////////////////////////////////////////////////////////////////
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			
		upload: function(method, route, options) {
			var u, request;

			request = this._(prepareRequest)(method, route);
			request.url = Berta.URLBase.REST + request.url;

			u = new Berta.Server.Upload(request, options);
			u.start();

			this.uploaders[++this.curUploaderIndex] = u;
			return this.curUploaderIndex;
		},
		cancelUpload: function(uploaderIndex) {
			if(this.uploaders[uploaderIndex]) {
				this.uploaders[uploaderIndex].cancel();
				this.uploaders[uploaderIndex].destroy();
				this.uploaders[uploaderIndex] = null;
			}
		},
		
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//  UTILITY   //////////////////////////////////////////////////////////////////////////////////////////////////////////
		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	
		cleanup: function() {
			for(var i = 0; i < this.uploaders.length; i++) {
				if(this.uploaders[i]) this.uploaders[i].destroy();
			}
			
			for(var i = 0; i < this.xhrs.length; i++) {
				if(this.xhrs[i] !== null) {
					this.xhrs[i].abort();
				}
			}
		}
		
	};
	
	
	
	//   .______   .______     __  ____    ____  ___   .___________. _______    //////////////////////////////////////////////////
	//   |   _  \  |   _  \   |  | \   \  /   / /   \  |           ||   ____|   //////////////////////////////////////////////////
	//   |  |_)  | |  |_)  |  |  |  \   \/   / /  ^  \ `---|  |----`|  |__      //////////////////////////////////////////////////
	//   |   ___/  |      /   |  |   \      / /  /_\  \    |  |     |   __|     //////////////////////////////////////////////////
	//   |  |      |  |\  \--.|  |    \    / /  _____  \   |  |     |  |____    //////////////////////////////////////////////////
	//   | _|      | _| `.___||__|     \__/ /__/     \__\  |__|     |_______|   //////////////////////////////////////////////////
	
	/**
	 * Parses short-hand requests
	 * 
	 * @param	method	GET,POST,PUT,DELETE
	 * @param	route	Berta.Route
	 */
	/*function prepareRequest(method, command, data, params) {
		var r = {}, isFileUpload = false;
		
		//$.log('prepare ', method, command, data, params);
		
		if (!method || !command) {
			$.log('Server: bad request (', method, ' ', command, ') -> ', data);
			r = null;
		
		} else {
			var commandSplit = command.split(':');
			//$.log(commandSplit);
			
			////////////////////////////////////////////////////////////////////////////////////////////////////
			// 1. parse the command
			////////////////////////////////////////////////////////////////////////////////////////////////////
			
			// commands can come in form "s:<group>.<item>", omitting the slash
			if(commandSplit[0].substr(0, 1) !== '/') {
				commandSplit[0] = '/' + commandSplit[0];
			}
			
			// expand the short-hand versions
			if(commandSplit[0] == '/s') commandSplit[0] = '/settings';
			if(commandSplit[0] == '/t') commandSplit[0] = '/template-settings';
			
			// put the variable in appropriate array (entry, setting, section, ...)
			// the dataVarContainer will be used also later for params
			var dataVarContainer = false;
			if(commandSplit[1]) {
				if (commandSplit[1].indexOf('[') > 0 && commandSplit[1].indexOf(']') > 0) {
					// if the variable already contains a container then just get it
					dataVarContainer = commandSplit[1].substr(0, commandSplit[1].indexOf('['));
				
				} else {
					// if there is no container present in the variable name, then guess it from the REST
					switch (true) {
						case commandSplit[0].indexOf('/settings') === 0 || commandSplit[0].indexOf('/template-settings') === 0:
							dataVarContainer = 'setting';
							break;
						case commandSplit[0].indexOf('/entries') === 0:
							dataVarContainer = 'entry';
							break;
						case commandSplit[0].indexOf('/images') === 0:
							dataVarContainer = 'entry_image';
							break;
						case commandSplit[0].indexOf('/sections') === 0:
							dataVarContainer = 'section';
							break;
					}
					if (dataVarContainer) {
						commandSplit[1] = dataVarContainer + '[' + commandSplit[1] + ']';
					}
				}
			}
			
			// settings and images always need JSON
			if((commandSplit[0].indexOf('/settings') === 0
				|| commandSplit[0].indexOf('/template-settings') === 0
				|| commandSplit[0].indexOf('/images') === 0)
				&& commandSplit[0].substr(commandSplit[0].length - 5) !== '.json') {
				commandSplit[0] += '.json'; 
			}

			r.url = commandSplit[0];
			r.method = method;
			
			////////////////////////////////////////////////////////////////////////////////////////////////////
			// 2. parse data
			////////////////////////////////////////////////////////////////////////////////////////////////////

			// if command comes in form /path/to/rest:path.to.variable and data is not an object
			// then make url just out of the REST part, and objectify data
			if ('string' == typeof(data) || 'number' == typeof(data) || 'boolean' == typeof(data)) {
				if (commandSplit[1]) {
					r.data = {};
					r.data[commandSplit[1]] = data;
				} else {
					// can't have string or integer data without a variable name
					$.logError('Server: data type requires a variable (' + method + ' ' + command + ') -> ', data);
					r = null;
				}
				
			// FileList object for file upload
			} else if (typeof(data) === 'object' && data instanceof FileList) {
				r.data = {};
				r.files = {
					variable: commandSplit[1] ? commandSplit[1] : 'path',
					list: data
				};
					
				isFileUpload = true;
			
			// "no data" is also ok
			} else if (data === undefined || data === null) {
				r.data = {};
				if (commandSplit[1]) {
					r.data[commandSplit[1]] = '';
				}
				
			// if data is an object, then use it only if there is no  variable part in the url
			} else if (typeof(data) === 'object') {
				if (commandSplit[1]) {
					// can't have proper data objects (that represent the whole POST)
					// tohether with a variable, that allows only for sending strings and numbers
					$.logError('Server: parameter mismatch (', method, ' ', command, ') -> ', data);
					r = null;
				} else {
					r.data = data;
				}
				
			} else {
				$.logError('Server: unsupported data type (' + method + ' ' + command + ') -> ', data);
				r = null;
			}
			

			////////////////////////////////////////////////////////////////////////////////////////////////////
			// 3. add params
			////////////////////////////////////////////////////////////////////////////////////////////////////

			// add params, if any
			if(r !== null && params) {
				for(var p in params) {
					if(params.hasOwnProperty(p)) {
						// params always should be passed WITHOUT container variable,
						// it will be added here
						r.data[dataVarContainer ? (dataVarContainer + '[' + p + ']') : p] = 
							params[p] !== null ? params[p] : '';	// Null params turn to empty strings
					}
				}
			} 
			
			
			////////////////////////////////////////////////////////////////////////////////////////////////////
			// 4. post-process
			////////////////////////////////////////////////////////////////////////////////////////////////////
			
			// finally, replace PUT and DELETE with supported methods
			if (r !== null) {
				if (method == 'put') {
					r.data['sf_method'] = 'put';	// sf_method goes into POST, not GET params
					//r.url += (r.url.indexOf('?') >= 0 ? '&' : '?') + 'sf_method=put';
					r.method = 'post';
				} else if (method == 'delete') {
					r.data['sf_method'] = 'delete';
					//r.url += (r.url.indexOf('?') >= 0 ? '&' : '?') + 'sf_method=delete';
					r.method = 'post';
				}
			}
			
		}
		
		//$.log(r.method, r.url, r.data);
		return r;
	}*/
	function prepareRequest(method, route) {
		var r;
		
		if (!method || !route) {
			$.log('Server: bad request (', method, ' ', route, ')');
			r = null;
		
		} else {
			
			r = route;
			r.method = method;
			
			// correct METHOD
			if(typeof r.data != 'object' || r.data === null) r.data = {};
			r.data['sf_method'] = method;
			if (method == 'put') {
				r.data['sf_method'] = 'put';
				r.method = 'post';
			} else if (method == 'delete') {
				r.data['sf_method'] = 'delete';
				r.method = 'post';
			}

		}

		return r;
	}
	
	
	
	function ajax(method, route, callbackComplete, callbackError, isRESTCommand) {
		//$.log('Server::ajax ', route, isRESTCommand);
		
		var request = this._(prepareRequest)(method, route);
		//$.log('request', (isRESTCommand ? Berta.URLBase.REST : Berta.URLBase.files) + request.url);
		
		if (request) {
			var xhrId = this.xhrCount++,
				self = this,
				urlBase = isRESTCommand ? Berta.URLBase.REST : Berta.URLBase.files;
			
			// carry out the actual request
			this.xhrs[xhrId] = jQuery.ajax({
				type: request.method,
				url: urlBase + request.url,
				data: request.data,
				
				complete: function(xhrObj, requestStatus){
					var response = new Berta.Server.Response(xhrObj);
					self._(ajaxComplete)(xhrId, route, response, callbackComplete);
				},
				error: function(xhrObj, requestStatus){
					var response = new Berta.Server.Response(xhrObj);
					self._(ajaxError)(xhrId, route, response, callbackError);
				}
			});
			
			return xhrId;
		}
		
		return null;
	}
	function ajaxComplete(id, route, response, callback) {
		//console.debug('XHR(' + id + ') complete: ', response);
		//console.debug(jQuery.isFunction(callback));
		if(jQuery.isFunction(callback)) callback(id, response);
		this.xhrs[id] = null;
	}
	function ajaxError(id, route, response, callback) {
		$.logError('Server: ', this.xhrs[id], "(" + id + ") error: ", response.status, response.errorMessage);
		if(callback && callback.call) callback(id, response);
		this.xhrs[id] = null;
	}
	
	
}());



Berta.Server.Response = function(xhrObj) {
	
	this.error = xhrObj.status == 0 || xhrObj.status >= 300;
	this.errorMessage = this.error ? (xhrObj.statusText ? xhrObj.statusText : 'Connection Error') : '';
	
	this.status = xhrObj.status;
	this.code = this.status;
	
	this.text = xhrObj.responseText;
	this.json = null;
	
	var t = this.text.trim();
	if (t[0] === '{' || t[0] === '[') {
		try {
			this.json = JSON.parse(t);
		} catch(e) {
			
		}
	}
	
	if (this.json) {
		if("errors" in this.json) {
			// managed validator errors return an object with "errors" key
			this.error = true;
			this.errorMessage = xhrObj.responseText;	// TODO: fish out the error messages from JSON
		
		} else if("error" in this.json) {
			// symfony errors (500) will return an object with "error" key
			this.error = true;
			this.errorMessage = this.json.error.message;
		}
	}
	
};


